﻿using System;
using System.IO;
using System.Net;
using System.Web.Configuration;
using System.Xml;

namespace AMAP
{
    // Resolve addresses into latitude/longitude coordinates using Google MAP API webservices
    public static class GeoCoder
    {
        private static Geolocation? GetGPSCoordinates(string Query)
        {
            XmlNode Coordinates = null;

            try
            {
                string xmlString = GetUrl(string.Format("http://maps.google.com/maps/geo?q={0}&output=xml&key=" + WebConfigurationManager.AppSettings["GoogleMapsKey"].ToString(), Query));
                XmlDocument xd = new XmlDocument();

                xd.LoadXml(xmlString);
                XmlNamespaceManager xnm = new XmlNamespaceManager(xd.NameTable);
                Coordinates = xd.GetElementsByTagName("coordinates")[0];
            }
            catch (Exception)
            {
            }

            Geolocation? gl = null;

            if (Coordinates != null)
            {
                string[] coordinatesArray = Coordinates.InnerText.Split(',');

                if (coordinatesArray.Length >= 2)
                {
                    gl = new Geolocation
                    {
                        Latitude = Convert.ToDouble(coordinatesArray[1].ToString().Replace(".", ",")),
                        Longitude = Convert.ToDouble(coordinatesArray[0].ToString().Replace(".", ","))
                    };
                }
            }

            return gl;
        }

        public static Geolocation? GetGPSCoordinates(string Address1, string PostalCode, string City, string Country)
        {
            return GetGPSCoordinates(Address1 + ", " + PostalCode + " " + City + " " + Country);
        }

        public static Geolocation? GetGPSCoordinates(string PostalCode, string City, string Country)
        {
            return GetGPSCoordinates(PostalCode + " " + City + " " + Country);
        }

        public static Geolocation? GetGPSCoordinates(string PostalCode, string Country)
        {
            return GetGPSCoordinates(PostalCode + " " + Country);
        }

        ///<summary>
        /// Retrieve a Url via WebClient
        /// </summary>
        private static string GetUrl(string url)
        {
            string Result = String.Empty;
            WebClient Client = new WebClient();
            Stream Stream = Client.OpenRead(url);
            StreamReader sr = new StreamReader(Stream);

            Result = sr.ReadToEnd();

            return Result;
        }

        /// <summary>
        /// Computes dand returns GPS coordinates based on given address
        /// </summary>
        /// <param name="Coordinates">The coordinates that contains the searched address</param>
        /// <returns>The given parameter updated with coordinates</returns>
        public static Domain.Coordinates SetGPSCoordinates(Domain.Coordinates Coordinates)
        {
            Geolocation? GPSCoordinates = GetGPSCoordinates(Coordinates.Address1, Coordinates.City, Coordinates.PostalCode, "FRANCE") ?? null;
            Geolocation Location = (GPSCoordinates.HasValue ? GPSCoordinates.Value : new Geolocation { Latitude = 0, Longitude = 0 });
            Coordinates.Latitude = Location.Latitude;
            Coordinates.Longitude = Location.Longitude;

            return Coordinates;
        }

        /// <summary>
        /// Computes and returns the distance between to point A and B represented by two Coordinates POCO
        /// </summary>
        /// <param name="A">First point</param>
        /// <param name="B">Second point</param>
        /// <returns>A double that represents the distance between the two points</returns>
        /// <remarks>The result distance is given in kilometers and is based on a 6371 earth radius</remarks>
        public static double GetDistance(Domain.Coordinates A, Domain.Coordinates B)
        {
            double LatARad = (Math.PI * A.Latitude / 180);
            double LonARad = (Math.PI * A.Longitude / 180);
            double LatBRad = (Math.PI * B.Latitude / 180);
            double LonBRad = (Math.PI * B.Longitude / 180);
            return 6371 * (Math.Acos(Math.Cos(LatARad) * Math.Cos(LatBRad) * Math.Cos(LonARad) * Math.Cos(LonBRad) + Math.Cos(LatARad) * Math.Sin(LonARad) * Math.Cos(LatBRad) * Math.Sin(LonBRad) + Math.Sin(LatARad) * Math.Sin(LatBRad)));
        }

        /// <summary>
        /// Computes and returns the distance between A coordinates and the given GPS coordinates
        /// </summary>
        /// <param name="A">Point A coordinates</param>
        /// <param name="Latitude">Latitude of point B</param>
        /// <param name="Longitude">Longitude of point B</param>
        /// <returns>A double that represents the distance between the two points</returns>
        /// <remarks>The result distance is given in kilometers and is based on a 6371 earth radius</remarks>
        public static double GetDistance(Domain.Coordinates A, double Latitude, double Longitude)
        {
            return GetDistance(A, new Domain.Coordinates { Latitude = Latitude, Longitude = Longitude });
        }

        /// <summary>
        /// Computes and returns the distance between A coordinates and the given delivery place
        /// </summary>
        /// <param name="A">Point A coordinates</param>
        /// <param name="B">Delivery place, point B</param>
        /// <returns>A double that represents the distance between the two points</returns>
        /// <remarks>The result distance is given in kilometers and is based on a 6371 earth radius</remarks>
        public static double GetDistance(Domain.Coordinates A, Domain.Place B)
        {
            Geolocation? Tmp = GetGPSCoordinates(B.Address1, B.PostalCode, B.City, "FRANCE");
            Geolocation Location = (Tmp.HasValue ? Tmp.Value : new Geolocation { Latitude = 0, Longitude = 0 });
            return GetDistance(A, new Domain.Coordinates { Latitude = Location.Latitude, Longitude = Location.Longitude });
        }

        public static double GetDistance(Domain.Coordinates coordinates, Domain.Amap Amap)
        {
            return GetDistance(coordinates, Amap.Latitude, Amap.Longitude);
        }
    }

    public struct Geolocation
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
    }
}
